package spacewar2.clients;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import spacewar2.actions.DoNothingAction;
import spacewar2.actions.MoveAction;
import spacewar2.actions.MoveToObjectAction;
import spacewar2.actions.SpacewarAction;
import spacewar2.actions.SpacewarActionException;
import spacewar2.actions.SpacewarPurchaseEnum;
import spacewar2.objects.Asteroid;
import spacewar2.objects.Base;
import spacewar2.objects.Beacon;
import spacewar2.objects.Ship;
import spacewar2.objects.SpacewarActionableObject;
import spacewar2.objects.SpacewarObject;
import spacewar2.powerups.SpacewarPowerup;
import spacewar2.shadows.Shadow;
import spacewar2.simulator.Toroidal2DPhysics;
import spacewar2.utilities.Position;

/**
 * Collects nearby asteroids and brings them to the base, picks up beacons as needed for energy
 * 
 * @author amy
 */
public class HeuristicAsteroidCollectorTeamClient extends TeamClient {
	HashMap <UUID, Ship> asteroidToShipMap;
	boolean aimingForBase;

	/**
	 * Assigns ships to asteroids and beacons, as described above
	 */
	public Map<UUID, SpacewarAction> getMovementStart(Toroidal2DPhysics space,
			Set<SpacewarActionableObject> actionableObjects) {
		HashMap<UUID, SpacewarAction> actions = new HashMap<UUID, SpacewarAction>();

		// loop through each ship
		for (SpacewarObject actionable :  actionableObjects) {
			if (actionable instanceof Ship) {
				Ship ship = (Ship) actionable;
				SpacewarAction current = ship.getCurrentAction();
				Position currentPosition = ship.getPosition();

				// aim for a beacon if there isn't enough energy
				if (ship.getEnergy() < 2000) {
					Beacon beacon = pickNearestBeacon(space, ship);
					SpacewarAction newAction = null;
					// if there is no beacon, then just skip a turn
					if (beacon == null) {
						newAction = new DoNothingAction();
					} else {
						newAction = new MoveToObjectAction(space, currentPosition, beacon);
					}
					actions.put(ship.getId(), newAction);
					aimingForBase = false;
					continue;
				}

				// if the ship has enough money, take it back to base
				if (ship.getMoney() > 500) {
					Base base = findNearestBase(space, ship);
					SpacewarAction newAction = new MoveToObjectAction(space, currentPosition, base);
					actions.put(ship.getId(), newAction);
					aimingForBase = true;
					continue;
				}

				// did we bounce off the base?
				if (ship.getMoney() == 0 && ship.getEnergy() > 2000 && aimingForBase) {
					current = null;
					aimingForBase = false;
				}

				// otherwise aim for the asteroid
				if (current == null || current.isMovementFinished(space)) {
					aimingForBase = false;
					Asteroid asteroid = pickHighestValueFreeAsteroid(space, ship);

					SpacewarAction newAction = null;

					if (asteroid == null) {
						// there is no asteroid available so collect a beacon
						Beacon beacon = pickNearestBeacon(space, ship);
						// if there is no beacon, then just skip a turn
						if (beacon == null) {
							newAction = new DoNothingAction();
						} else {
							newAction = new MoveToObjectAction(space, currentPosition, beacon);
						}
					} else {
						asteroidToShipMap.put(asteroid.getId(), ship);
						newAction = new MoveToObjectAction(space, currentPosition, asteroid);
					}
					actions.put(ship.getId(), newAction);
				} else {
					actions.put(ship.getId(), ship.getCurrentAction());
				}
			} else {
				// it is a base.  Heuristically decide when to use the shield (TODO)
				actions.put(actionable.getId(), new DoNothingAction());
			}
		} 
		return actions;
	}

	/**
	 * Find the base for this team nearest to this ship
	 * 
	 * @param space
	 * @param ship
	 * @return
	 */
	private Base findNearestBase(Toroidal2DPhysics space, Ship ship) {
		double minDistance = Double.MAX_VALUE;
		Base nearestBase = null;
		
		for (Base base : space.getBases()) {
			if (base.getTeamName().equalsIgnoreCase(ship.getTeamName())) {
				double dist = space.findShortestDistance(ship.getPosition(), base.getPosition());
				if (dist < minDistance) {
					minDistance = dist;
					nearestBase = base;
				}
			}
		}
		return nearestBase;
	}

	/**
	 * Returns the asteroid of highest value that isn't already being chased by this team
	 * 
	 * @return
	 */
	private Asteroid pickHighestValueFreeAsteroid(Toroidal2DPhysics space, Ship ship) {
		ArrayList<Asteroid> asteroids = space.getAsteroids();
		int bestMoney = Integer.MIN_VALUE;
		Asteroid bestAsteroid = null;

		for (Asteroid asteroid : asteroids) {
			if (!asteroidToShipMap.containsKey(asteroid)) {
				if (asteroid.isMineable() && asteroid.getMoney() > bestMoney) {
					bestMoney = asteroid.getMoney();
					bestAsteroid = asteroid;
				}
			}
		}
		//System.out.println("Best asteroid has " + bestMoney);
		return bestAsteroid;
	}

	/**
	 * Find the nearest beacon to this ship
	 * @param space
	 * @param ship
	 * @return
	 */
	private Beacon pickNearestBeacon(Toroidal2DPhysics space, Ship ship) {
		// get the current beacons
		ArrayList<Beacon> beacons = space.getBeacons();

		Beacon closestBeacon = null;
		double bestDistance = Double.POSITIVE_INFINITY;

		for (Beacon beacon : beacons) {
			double dist = space.findShortestDistance(ship.getPosition(), beacon.getPosition());
			if (dist < bestDistance) {
				bestDistance = dist;
				closestBeacon = beacon;
			}
		}

		return closestBeacon;
	}



	@Override
	public void getMovementEnd(Toroidal2DPhysics space, Set<SpacewarActionableObject> actionableObjects) {
		ArrayList<Asteroid> finishedAsteroids = new ArrayList<Asteroid>();

		for (UUID asteroidId : asteroidToShipMap.keySet()) {
			Asteroid asteroid = (Asteroid) space.getObjectById(asteroidId);
			if (!asteroid.isAlive()) {
				finishedAsteroids.add(asteroid);
				//System.out.println("Removing asteroid from map");
			}
		}

		for (Asteroid asteroid : finishedAsteroids) {
			asteroidToShipMap.remove(asteroid);
		}


	}

	@Override
	public void initialize() {
		asteroidToShipMap = new HashMap<UUID, Ship>();
	}

	@Override
	public void shutDown() {
		// TODO Auto-generated method stub

	}

	@Override
	public Set<Shadow> getNewShadows() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Set<Shadow> getOldShadows() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	/**
	 * If there is enough money, buy a base.  Place it by finding a ship that is sufficiently
	 * far away from the existing bases
	 */
	public Map<UUID, SpacewarPurchaseEnum> getTeamPurchases(Toroidal2DPhysics space,
			Set<Ship> ships, int availableMoney, int currentCostNewBase) {

		HashMap<UUID, SpacewarPurchaseEnum> purchases = new HashMap<UUID, SpacewarPurchaseEnum>();
		double BASE_BUYING_DISTANCE = 200;

		if (availableMoney >= currentCostNewBase) {
			for (Ship ship : ships) {
				ArrayList<Base> bases = space.getBases();
				
				// how far away is this ship to a base of my team?
				double maxDistance = Double.MIN_VALUE;
				for (Base base : bases) {
					if (base.getTeamName().equalsIgnoreCase(getTeamName())) {
						double distance = space.findShortestDistance(ship.getPosition(), base.getPosition());
						if (distance > maxDistance) {
							maxDistance = distance;
						}
					}
				}
				
				if (maxDistance > BASE_BUYING_DISTANCE) {
					purchases.put(ship.getId(), SpacewarPurchaseEnum.BASE);
					//System.out.println("Buying a base!!");
					break;
				}
			}		
		} 
		
		return purchases;
	}

	@Override
	public Map<UUID, SpacewarPowerup> getPowerups(Toroidal2DPhysics space,
			Set<SpacewarActionableObject> actionableObjects) {
		// TODO Auto-generated method stub
		return null;
	}

}
